package com.itealabs.postjson.ext;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.map.DeserializationConfig;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.SerializationConfig;
import org.codehaus.jackson.type.TypeReference;

import com.itealabs.postjson.JsonCall;
import com.itealabs.postjson.JsonCallWaiter;
import com.itealabs.postjson.JsonReturn;
import com.itealabs.postjson.JsonReturn.State;

/**
 * 
 * @version $Id: ReflectionJsonCallWaiter.java 19 2010-05-06 13:50:17Z iteahere $
 * @author itea
 * 2010-4-16
 */
public class ReflectionJsonCallWaiter implements JsonCallWaiter {

	private Object pojo = this;
	
	private final ThreadLocal<Map<String, Object>> threadLocal
		= new ThreadLocal<Map<String, Object>>();
	
	private final ObjectMapper jsonMapper = new ObjectMapper();
	
	public ReflectionJsonCallWaiter() {	
		jsonMapper.configure(DeserializationConfig.Feature.USE_BIG_DECIMAL_FOR_FLOATS, true);
		jsonMapper.configure(SerializationConfig.Feature.CAN_OVERRIDE_ACCESS_MODIFIERS, false);
	}
	
	public ReflectionJsonCallWaiter(Object pojo) {
		this();
		if(pojo == null) throw new NullPointerException();
		this.pojo = pojo;
	}
	
	private Method getMethodByName(String name) {
		Method[] ms = pojo.getClass().getDeclaredMethods();
		for(Method m : ms)
			if(m.getName().equals(name)) return m;
		return null;
	}
	
	private JsonReturn wrapInvoke(Method m, Object[] args)
	throws IllegalArgumentException, IllegalAccessException {
		try {
			return new JsonReturn(State.OK, m.invoke(pojo, args));
		} catch (InvocationTargetException e) {
			return new JsonReturn(State.EXCEPTION, e.getCause());
		}
	}
	
	protected final Map<String, Object> getContext() {
		return threadLocal.get();
	}
	
	public final JsonReturn service(JsonCall jsonCall, Map<String, Object> ctx) {
		// get method name
		Method method = getMethodByName(jsonCall.getMethod()
				.substring(jsonCall.getMethod().lastIndexOf(".") +1));
		
		threadLocal.set(ctx);
		try {
			return method == null
				? new JsonReturn(State.ERROR,new NoSuchMethodException(jsonCall.getMethod()))
				: wrapInvoke(method, buildArgs(method, jsonCall));
		} catch (Exception e) {
			return new JsonReturn(State.ERROR, e);
		} finally {
			threadLocal.remove();
		}
		
	}

	private List<JsonNode> getJsonNodeParams(Type[] paramClasses, JsonNode jn) {
		List<JsonNode> params = new ArrayList<JsonNode>(paramClasses.length);
		for(int i=0, j= Math.max(paramClasses.length, jn.size()); i<j; i++)
			params.add(i < jn.size() ? jn.get(i) : null);
		return params;
	}
	
	private static class TypeRef extends TypeReference<Type> {

		private final Type type;
		
		public TypeRef(Type type) {
			this.type = type;
		}

		@Override
		public Type getType() {
			return type;
		}
		
	}
	private Object[] buildArgs(Method method, JsonCall jsonCall) {
		
		Type[] paramTypes = method.getGenericParameterTypes();
		List<Object> args = new ArrayList<Object>(paramTypes.length);
		List<JsonNode> params = getJsonNodeParams(paramTypes, jsonCall.getData());
		
		for(int i=0; i < paramTypes.length; i++) {
			Type t = paramTypes[i];
			if(i == paramTypes.length -1 && method.isVarArgs()) {
				JsonNode[] vargs = new JsonNode[params.size() -i];
				for(int j=0; i<params.size(); i++, j++)
					vargs[j] = params.get(i);
				args.add(jsonMapper.convertValue(vargs, new TypeRef(t)));
			} else if(i == paramTypes.length -1 && params.get(i) == null
					&& "java.util.Map<java.lang.String, java.lang.Object>".equals(t.toString()))
				args.add(getContext());
			else
				args.add(jsonMapper.convertValue(params.get(i), new TypeRef(t)));
		}
		return args.toArray(new Object[0]);
	}
	
	public Object getPojo() {
		return pojo;
	}

	public void setPojo(Object pojo) {
		this.pojo = pojo;
	}
}
